package com.beiji.lib.pen.ui.view

import android.content.Context
import android.graphics.*
import android.os.Looper
import android.util.AttributeSet
import android.util.Log
import android.util.SparseArray
import android.view.Gravity
import android.view.View
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.ScrollView
import androidx.core.widget.NestedScrollView
import com.beiji.lib.pen.PenLog
import com.beiji.lib.pen.R
import com.beiji.lib.pen.cache.BitmapCacheModel
import com.beiji.lib.pen.cache.NoteCacheModel
import com.beiji.lib.pen.cache.PenCacheManager
import com.beiji.lib.pen.cache.PenDotCacheHelper
import com.beiji.lib.pen.constants.PaletteColor
import com.beiji.lib.pen.constants.PaperSize
import com.beiji.lib.pen.device.PenT111
import com.beiji.lib.pen.model.DotUnit
import com.beiji.lib.pen.model.PenStroke
import com.beiji.lib.pen.screenDensity
import com.beiji.lib.pen.ui.renderer.StrokeTransformHelper
import com.tqltech.tqlpencomm.bean.Dot

import kotlinx.coroutines.CommonPool
import kotlinx.coroutines.android.UI
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.util.concurrent.ArrayBlockingQueue
import java.util.concurrent.BlockingQueue

/**
 * 笔迹View
 * Created by X on 2018/6/14.
 */
class StrokeView(context: Context, attrs: AttributeSet) : ScrollView(context, attrs) {
    companion object {
        private const val TAG = "StrokeView"
    }

    enum class EditMode {
        STROKE,
        ERASER,
        MARK
    }

    //笔记本页面的size
    var paperSize = PaperSize.A5

    val dotTransformMatrix: Matrix = Matrix()

    private var contentLayout: LinearLayout = LinearLayout(context)
    var pageMap: SparseArray<StrokePageView> = SparseArray()

    private var cacheModel: NoteCacheModel? = null
    var isDrawDotBg: Boolean = true

    private var editMode = EditMode.STROKE
    private var currentPageView: StrokePageView? = null
    private var eraserRadiusRatio = 24f
    internal val paint = Paint().apply {
        style = Paint.Style.STROKE
        isAntiAlias = true
        strokeCap = Paint.Cap.ROUND
        strokeJoin = Paint.Join.ROUND
        pathEffect = CornerPathEffect(100f)
        strokeMiter = 1.0f
    }
    internal val paintMark = Paint()
    internal val baseStrokeWidth: Float = context.screenDensity
    private var strokeColor: PaletteColor = PaletteColor.COLOR_0

    private var dotQueue: BlockingQueue<DotUnit> = ArrayBlockingQueue<DotUnit>(10000)


    fun setEditMode(editMode: EditMode) {
        this.editMode = editMode
        currentPageView?.reset()
        //切换笔迹类型时，如果正在书写，要切断这个笔画，人为添加一个pen_up事件
        PenDotCacheHelper.cacheDot(DotUnit().apply {
            type = Dot.DotType.PEN_UP
        })
    }

    fun getEditMode(): EditMode {
        return editMode
    }


    /**
     * 设置笔迹颜色
     * @param caching 是否正在缓存笔迹，是的话，同时也要改变缓存的颜色，默认为false（比如回放的时候）
     */
    fun setStrokeColor(color: PaletteColor, caching: Boolean = false) {
//        PenLog.e("setStrokeColor -> ${color.name} , $caching")
        strokeColor = color
        paint.color = strokeColor.toColor()
        if (caching) {
            PenDotCacheHelper.strokeColor = color
        }
    }

    fun setMarkColor(color: PaletteColor, caching: Boolean = false) {
        PenLog.e("setMarkColor -> ${color.name} , $caching")
        strokeColor = color
        paintMark.color = strokeColor.toColor()
        paintMark.alpha = 63
        if (caching) {
            PenDotCacheHelper.strokeColor = color
        }
    }


    fun setEraserRadius(eraserRadius: Float) {
        this.eraserRadiusRatio = eraserRadius
    }

    fun getEraserRadius(): Float {
        return eraserRadiusRatio
    }

    init {
        paint.apply {
            isAntiAlias = true
            strokeWidth = baseStrokeWidth
            color = strokeColor.toColor()
            style = Paint.Style.STROKE
            strokeJoin = Paint.Join.ROUND
            strokeCap = Paint.Cap.ROUND
            strokeMiter = 0.3f
        }
        paintMark.apply {
            isAntiAlias = true
            strokeWidth = baseStrokeWidth * 8
            style = Paint.Style.STROKE
            strokeJoin = Paint.Join.ROUND
            strokeCap = Paint.Cap.ROUND
            color = strokeColor.toColor()
            strokeMiter = 3f
            alpha = 63
//            xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OVER)
        }

        contentLayout.orientation = LinearLayout.VERTICAL
        contentLayout.gravity = Gravity.CENTER_HORIZONTAL
        this.addView(contentLayout)
    }

    /**
     * 开始创建缓存，在使用数码笔之前必须调用此方法
     * 绘制历史数据  适配BookID设置背景
     */
    fun startCache(key: String, penStrokeCallback: ((PenStroke) -> Unit)? = null) {
        PenLog.e("start cache.")
        //绘制存储的笔迹
        cacheModel = PenCacheManager.instance.startCache(key, cacheLoadedCallback)
        if (cacheModel!!.bitmapCacheList.size > 0) {
            if (cacheModel!!.bitmapCacheList[0].bookId == 3) {
                setBackgroundResource(R.drawable.note_edit_bg_a6_line)
            } else if (cacheModel!!.bitmapCacheList[0].bookId == 4) {
                setBackgroundResource(R.drawable.note_edit_bg_a5_line)
            } else if (cacheModel!!.bitmapCacheList[0].bookId == 30) {
                setBackgroundResource(R.drawable.note_edit_bg_a5_30)
            } else if (cacheModel!!.bitmapCacheList[0].bookId == 100) {
                setBackgroundResource(R.drawable.techpage)
                setBackgroundResource(R.drawable.note_edit_bg_a5_100)
            } else if (cacheModel!!.bitmapCacheList[0].bookId == 101) {
                setBackgroundResource(R.drawable.note_edit_bg_a5_101)
            } else {
                setBackgroundResource(R.drawable.note_edit_bg)
            }
        } else {
            setBackgroundResource(R.drawable.note_edit_bg)
        }
        PenDotCacheHelper.penStrokeCacheHandler = {
            cacheModel!!.cachePenStroke(it)
            penStrokeCallback?.invoke(it)
        }
        cacheModel!!.onCacheToFile = {
            cacheBitmap()
        }
    }

    private val cacheLoadedCallback: ((cacheModel: NoteCacheModel) -> Unit)? = {
        this.post {
            val cacheBitmapList = it.bitmapCacheList
            if (cacheBitmapList.isEmpty()) {
                return@post
            }
            cacheBitmapList.forEach {
                addNewPage(it.realPageNumber, it.bookId)
            }
        }
    }

    fun loadCache(key: String) {
        cacheModel = PenCacheManager.instance.loadCache(key, cacheLoadedCallback)
    }

    fun loadCacheAsBackground(key: String, cacheLoadedCallback: ((cacheModel: NoteCacheModel) -> Unit)? = null) {
        cacheModel = PenCacheManager.instance.loadCache(key) {
            this.post {
                cacheLoadedCallback?.invoke(it)
                val cacheBitmapList = it.bitmapCacheList
                if (cacheBitmapList.isEmpty()) {
                    return@post
                }
                cacheBitmapList.forEach {
                    addNewPage(it.realPageNumber, it.bookId, true)
                }
            }
        }
    }

    private fun cacheBitmap() {
        Log.i(TAG, "cacheBitmap: ")
        val cacheBitmapList = cacheModel!!.bitmapCacheList
        for (i in 0 until contentLayout.childCount) {
            val pageView = contentLayout.getChildAt(i) as StrokePageView
            if (pageView.exportBitmap() == null) {
                continue
            }
            if (i >= cacheBitmapList.size) {
                val pageBitmapModel = BitmapCacheModel(
                        pageView.pageIndex, pageView.pageId, pageView.exportBitmap()!!, pageView.bookId)
                cacheBitmapList.add(pageBitmapModel)
            } else {
                cacheBitmapList[i].bitmap = pageView.exportBitmap()!!
            }
        }
    }

    fun endCache() {
        PenDotCacheHelper.penStrokeCacheHandler = null
        if (cacheModel == null) {
            return
        }
        cacheBitmap()
        PenCacheManager.instance.endCache()
        PenLog.e("end cache.")
    }

    /**
     * 清清除所有笔迹
     */
    fun clearAllStroke() {
        clearAllStroke(false)
    }

    fun clear() {
        clearAllStroke(true)
    }

//    fun clearAllStrokes() {
//        clearAllStrokes(false)
//    }

    /**
     * 清清除所有笔迹
     */
    fun clearAllStroke(removeView: Boolean) {
        for (i in 0 until contentLayout.childCount) {
            val pageView = contentLayout.getChildAt(i) as StrokePageView
            pageView.clearStroke()
        }
        pageMap.clear()
        if (removeView) {
            contentLayout.removeAllViews()
        }
    }

//    fun clearAllStrokes(removeView: Boolean) {
//        for (i in 0 until contentLayout.childCount) {
//            val pageView = contentLayout.getChildAt(i) as StrokePageView
//            pageView.clearStroke()
//        }
////        pageMap.clear()
//        if (removeView) {
//            contentLayout.removeAllViews()
//        }
//    }

    //适配BookID的比例大小  适配BookID
    private fun getBoardWidth(): Int = width
    private fun getBoardHeight(bookId: Int): Int = (when (bookId) {
        3 -> {
            (getBoardWidth().toFloat() / PenT111.A6_PAGE_WIDTH * PenT111.A6_PAGE_HEIGHT).toInt()
        }
        4 -> {
            (getBoardWidth().toFloat() / paperSize.width * paperSize.height).toInt()
        }
        30 -> {
            (getBoardWidth().toFloat() / PenT111.A5_100_WIDTH * PenT111.A5_100_HEIGHT).toInt()
        }
        100 -> {
            (getBoardWidth().toFloat() / PenT111.B5_100_WIDTH * PenT111.B5_100_HEIGHT).toInt()
        }
        101 -> {
            (getBoardWidth().toFloat() / PenT111.A5_101_WIDTH * PenT111.A5_101_HEIGHT).toInt()
        }
        else -> {
            (getBoardWidth().toFloat() / PenT111.A5_PAGE_WIDTH * PenT111.A5_PAGE_HEIGHT).toInt()
        }
    })

    private fun getBoardHeight(): Int = (getBoardWidth().toFloat() / paperSize.width * paperSize.height).toInt()


    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        super.onLayout(changed, l, t, r, b)
        val scaleX = getBoardWidth().toFloat() / paperSize.width
        val scaleY = getBoardHeight().toFloat() / paperSize.height
        //坐标变换矩阵
        dotTransformMatrix.setScale(scaleX, scaleY, 0f, 0f)
    }

    /**
     * 将一个笔画画到对应的StrokePageView中
     * @param dot
     */
    fun drawDot(dot: DotUnit) {
        PenLog.d("DotUnit->${dot.toString()}")
        val addResult = dotQueue.offer(dot)
        if (!addResult) {
            PenLog.e("Dot queue is full! What are you doing!")
            return
        }
        if (width == 0 || height == 0) {
            PenLog.e("stroke view is not layout yet.")
            return
        }
        drainDots()
    }

    /**
     * 绘制dot数据
     */
    private fun drainDots() {
        while (true) {
            Log.i(TAG, "drainDots: -------------")
            val dot = dotQueue.poll()
            dot ?: break
            val transformedDot = StrokeTransformHelper.transformDot(dot, dotTransformMatrix)
            if (dot.bookId == 4) drawDotByPage(transformedDot) else drawDotByPage(dot)
        }
    }

    suspend fun drawStroke(stroke: PenStroke) {
        if (pageMap.indexOfKey(stroke.bookId * 10000 + stroke.page) < 0) {
            val newPageJob = async(UI) {
                addNewPage(stroke.page, stroke.bookId)
            }
            newPageJob.await()?.drawStroke(stroke)
        } else {
            val pageView = pageMap[stroke.bookId * 10000 + stroke.page]
            pageView.drawStroke(stroke)
        }
    }

    /**
     * 将笔迹文件中的数据重新渲染成笔迹并draw
     */
    fun drawStrokeFromCacheFileByPage(finishedCallback: (() -> Unit)?) {
        if (cacheModel == null || !cacheModel!!.getCacheFile().exists()) {
            return
        }
        val file = cacheModel!!.getCacheFile()
        clearAllStroke()
        val start = System.currentTimeMillis()
        launch(CommonPool) {
            val list = file.bufferedReader().readLines().drop(1)
            val map = mutableMapOf<Int, MutableList<PenStroke>>()
            list.forEach {
                val stroke = PenStroke.fromJson(it) ?: return@forEach
                PenLog.e(stroke.toString())
                if (map[stroke.page] == null) {
                    val penPathList = mutableListOf<PenStroke>()
                    penPathList.add(stroke)
                    map[stroke.page] = penPathList
                } else {
                    map[stroke.page]!!.add(stroke)
                }
            }
            map.forEach {
                val page = it.key
                val strokeList = it.value
                if (pageMap.indexOfKey(strokeList[0].bookId * 10000 + page) < 0) {
                    val newPageJob = async(UI) {
                        addNewPage(page, strokeList[0].bookId)
                    }
                    newPageJob.await()?.restorePenStroke(strokeList)
                } else {
                    val pageView = pageMap[strokeList[0].bookId * 10000 + page]
                    pageView.restorePenStroke(strokeList)
                }
            }
            Log.e("costTime", "${System.currentTimeMillis() - start}")
            launch(UI) {
                finishedCallback?.invoke()
            }
        }
    }

    /**
     * 将笔迹文件中的数据重新渲染成笔迹并draw
     * 绘制历史笔迹
     */
    fun drawStrokeFromCacheFile(finishedCallback: (() -> Unit)? = null) {
        clearAllStroke(true)
        if (cacheModel == null || !cacheModel!!.getCacheFile().exists()) {
            setBackgroundResource(R.drawable.note_edit_bg)
            finishedCallback?.invoke()
            return
        }
        val start = System.currentTimeMillis()
        cacheModel?.bitmapCacheList?.clear()
        cacheModel?.readStrokesFromFile { strokeList ->
            if (strokeList.size == 0) {
                setBackgroundResource(R.drawable.note_edit_bg)
            }
            launch(CommonPool) {
                strokeList.forEach { stroke ->
                    if (stroke.action == PenStroke.ACTION_STROKE) {
                        setStrokeColor(PaletteColor.fromId(stroke.color))
                    } else if (stroke.action == PenStroke.ACTION_MARK) {
                        setMarkColor(PaletteColor.fromId(stroke.color))
                    }
                    PenLog.e(stroke.toString())
                    drawStroke(stroke)
                }
                Log.e("costTime", "${System.currentTimeMillis() - start}")
                cacheBitmap()
                cacheModel?.saveBitmapCache()
                launch(UI) {
                    finishedCallback?.invoke()
                }
            }
        }
    }

    /**
     * 添加新的一页到界面中
     */
    private fun doAddNewPage(pageId: Int, bookId: Int, bitmapAsBackground: Boolean = false): StrokePageView? {
        Log.i(TAG, "doAddNewPage: -------")
        if (pageMap.indexOfKey(bookId * 10000 + pageId) >= 0) {
//            if(pageMap[bookId*10000+pageId].bookId == bookId){
            return null
//            }
        }
        if (Thread.currentThread() == Looper.getMainLooper().thread) {

        }
        val pageView = StrokePageView(context, width, getBoardHeight(bookId), this)
        pageView.pageIndex = contentLayout.childCount
        pageView.pageId = pageId
        pageView.bookId = bookId
        pageView.tag = contentLayout.childCount
        pageMap.append((bookId * 10000 + pageId), pageView)

//        if (pageView.pageIndex == 0) {
//            val layoutParams = LinearLayout.LayoutParams(width, getBoardHeight(bookId))
//            layoutParams.gravity = Gravity.CENTER_HORIZONTAL
//            contentLayout.addView(pageView, layoutParams)
//            // Log.i(TAG, "doAddNewPage: "+width+""+getBoardHeight())
//        } else {
//            val layoutParams = LinearLayout.LayoutParams(width, getBoardHeight(bookId))
//            layoutParams.gravity = Gravity.CENTER_HORIZONTAL
//            contentLayout.addView(pageView, layoutParams)
//        }
        if (pageView.pageIndex == 0) {
            contentLayout.addView(pageView, width, getBoardHeight(bookId))
        } else {
            val layoutParams = LinearLayout.LayoutParams(width, getBoardHeight(bookId))
            contentLayout.addView(pageView, layoutParams)
            Log.i(TAG, "doAddNewPage:${pageView.pageIndex}//count=${contentLayout.childCount}")
//            for (i in 0 until contentLayout.childCount) {
//                if (i != pageView.tag) {
//                    var strokePageView = contentLayout.getChildAt(i) as StrokePageView
//                    strokePageView.recycleBitmap()
//                    Log.i(TAG, "doAddNewPage:i != pageView.tag")
//                } else {
//                    Log.i(TAG, "doAddNewPage:i = = pageView.tag")
//                }
//            }
        }

        setBackgroundColor(Color.parseColor("#FFFFFF"))
        if (cacheModel == null) {
            return pageView
        }
        val cacheBitmapList = cacheModel!!.bitmapCacheList
        if (cacheBitmapList.isNotEmpty() &&
                cacheBitmapList.size > pageView.pageIndex) {
            val penBitmapCacheModel = cacheBitmapList[pageView.pageIndex]
            if (bitmapAsBackground) {
                pageView.bgBitmap = penBitmapCacheModel.bitmap
            } else {
                pageView.strokeBitmap = penBitmapCacheModel.bitmap
            }
        }
        return pageView
    }

    /**
     * 添加新的一页到界面中
     */
    private fun addNewPage(pageId: Int, bookId: Int, bitmapAsBackground: Boolean = false): StrokePageView? {
        Log.i(TAG, "addNewPage: ------")
        if (pageMap.indexOfKey(bookId * 10000 + pageId) >= 0) {
//            if(pageMap[bookId*10000+pageId].bookId == bookId){
            return null
//            }
        }
        return if (Thread.currentThread() == Looper.getMainLooper().thread) {
            doAddNewPage(pageId, bookId, bitmapAsBackground)
        } else {
            runBlocking {
                val newPageJob = async(UI) {
                    doAddNewPage(pageId, bookId, bitmapAsBackground)
                }
                newPageJob.await()
            }
        }
    }

    /**
     * 将笔画画到StrokePageView中
     */
//    private fun drawDotByPage(dot: DotUnit) {
//        val pageId = dot.pageId
//        var currentPageView: StrokePageView? = null
//        for (i in 0 until contentLayout.childCount) {
//            val pageView = contentLayout.getChildAt(i) as StrokePageView
//            if (pageView.pageId == pageId) {
//                pageView.drawDot(dot)
//                currentPageView = pageView
//            }
//        }
//        if (currentPageView == null) {
//            currentPageView = addNewPage(pageId)
//            if (currentPageView != null) {
//                currentPageView.drawDot(dot)
//            }
//
//        }
//        if (currentPageView != null) {
//            scrollToCurPosition(dot, currentPageView.pageIndex)
//        }
//
//    }
    private fun drawDotByPage(dot: DotUnit) {
        val pageId = dot.pageId
        val bookId = dot.bookId
        currentPageView = null
        for (i in 0 until contentLayout.childCount) {
            val pageView = contentLayout.getChildAt(i) as StrokePageView
            if (pageView.pageId == pageId && pageView.bookId == bookId) {
                currentPageView = pageView
                Log.i("drawDotByPage0", "drawDotByPage: width=${currentPageView?.measuredWidth}///height=${currentPageView?.measuredHeight}")
                pageView.drawDot(dot)
            }
        }
        if (currentPageView == null) {
            currentPageView = addNewPage(pageId, bookId)
        }
        currentPageView?.let {
            Log.i("drawDotByPage1", "drawDotByPage: width=${currentPageView?.measuredWidth}///height=${currentPageView?.measuredHeight}")
            it.drawDot(dot)
            scrollToCurPosition(dot, it.pageIndex)
        }

    }

    private fun scrollToCurPosition(dot: DotUnit, pageIndex: Int) {
        val rect = Rect()
        contentLayout.getLocalVisibleRect(rect)
        val scrollToY = getBoardHeight(dot.bookId) * pageIndex + dot.y
        if (scrollToY > rect.top && scrollToY < rect.bottom) {
            return
        }
        this.post {
            smoothScrollTo(0, scrollToY.toInt())
        }
    }

    override fun onStopNestedScroll(target: View?) {
        super.onStopNestedScroll(target)

    }

    class TempPageMap(var pageSize: Int, var bookId: Int)
}